--------------------------------------------------------------------------------
-- CleverMacro v1.3.1 by _brain    zhCN by 凡人                               --
--------------------------------------------------------------------------------
local SEM = AceLibrary("SpecialEvents-Movement-2.0")
local lastUpdate = 0
local actions = {}
local macros = {}
local sequences = {}
local currentSequence
local actionEventHandlers = {}
local mouseOverResolvers = {}
local items = {}
local CM_ScanTip
local spellstatus = AceLibrary("SpellStatus-1.0")
--by Crazydru
local isbehind = true
local behindresettime = GetTime()

--检查是否有对应的BUFF材质
local function HasBuff(texture)
	for i = 1, 32 do
		local buff = UnitBuff("player", i)
		if buff and buff == [[Interface\Icons\]] .. texture then
			return true
		end
	end
	return false
end

--是否在移动
local function OnMoving()
	return SEM:PlayerMoving()
end

local function Trim(s)
	if s == nil then
		return nil
	end
	local _, b = string.find(s, "^%s*")
	local e = string.find(s, "%s*$", b + 1)
	return string.sub(s, b + 1, e - 1)
end

local function Seq(_, i)
	return (i or 0) + 1
end

local function Split(s, p, trim)
	local r, o = {}, 1
	repeat
		local b, e = string.find(s, p, o)
		if b == nil then
			local sub = string.sub(s, o)
			table.insert(r, trim and Trim(sub) or sub)
			return r
		end
		if b > 1 then
			local sub = string.sub(s, o, b - 1)
			table.insert(r, trim and Trim(sub) or sub)
		else
			table.insert(r, "")
		end
		o = e + 1
	until false
end

--by Crazydru 增加是否指向空处的识别位
function GetMouseOverUnit()
	local frame = GetMouseFocus()
	if frame then
		if frame.unit then
			return frame.unit, false
		end
		for _, fn in ipairs(mouseOverResolvers) do
			local unit = fn(frame)
			if unit then
				return unit, false
			end
		end
	end
	if UnitExists("mouseover") then
		return "mouseover", false
	elseif (UnitExists("target")) then
		return "mouseover";
	elseif (CS_SELF_ENABLED == true) then
		return "player";
	else
		return "mouseover", true
	end
end

--by Crazydru
--连击点
local function GetCP(cp)
	if string.find(cp, ">") then
		local biggercp = Split(cp, ">")
		return GetComboPoints("target") > tonumber(biggercp[2])
	elseif string.find(cp, "<") then
		local lesscp = Split(cp, "<")
		return GetComboPoints("target") < tonumber(lesscp[2])
	elseif cp == GetComboPoints("target") then
		return true
	end
	return false
end

--by Crazydru
--比较HP
local function Compare_UnitHp(v, unit)
	local m = false
	if unit == "mouseover" then
		local unit, m = GetMouseOverUnit()
	end
	for hp in v do
		local HPPercent = 100 / UnitHealthMax(unit) * UnitHealth(unit)
		if string.find(hp, ">") then
			local biggerhp = Split(hp, ">")
			return HPPercent > tonumber(biggerhp[2]), m
		elseif string.find(hp, "<") then
			local lesshp = Split(hp, "<")
			return HPPercent < tonumber(lesshp[2]), m
		end
	end
end

--by Crazydru
--比较MP
local function Compare_UnitMp(v, unit)
	local m = false
	if unit == "mouseover" then
		local unit, m = GetMouseOverUnit()
	end

	for mp in v do
		local MPPercent = 100 / UnitManaMax(unit) * UnitMana(unit)
		if string.find(mp, ">") then
			local biggermp = Split(mp, ">")
			return MPPercent > tonumber(biggermp[2]), m
		elseif string.find(mp, "<") then
			local lessmp = Split(mp, "<")
			return MPPercent < tonumber(lessmp[2]), m
		end
	end
end

--获取技能CD
local function GetSpellCooldownByName(spellName)
    local spell, rank = GetSpellInfo(spellName)
    local spellId and bookType = GetSpellIndex(spell, rank)
    if spellId, bookType then
        local _, duration = GetSpellCooldown(spellId, bookType)
        return duration
    else
        return nil
    end
end

--获取装备栏物品CD
local function GetInventoryCooldownByName(itemName)
	CM_ScanTip:SetOwner(UIParent, "ANCHOR_NONE")
	for i = 0, 19 do
		CM_ScanTip:ClearLines()
		hasItem = CM_ScanTip:SetInventoryItem("player", i)

		if hasItem then
			local lines = CM_ScanTip:NumLines()

			local label = getglobal("CM_ScanTipTextLeft1")

			if label:GetText() == itemName then
				local _, duration, _ = GetInventoryItemCooldown("player", i)
				return duration
			end
		end
	end

	return nil
end

--获取背包物品CD
local function GetContainerItemCooldownByName(itemName)
	CM_ScanTip:SetOwner(WorldFrame, "ANCHOR_NONE")

	for i = 0, 4 do
		for j = 1, GetContainerNumSlots(i) do
			CM_ScanTip:ClearLines()
			CM_ScanTip:SetBagItem(i, j)
			if CM_ScanTipTextLeft1:GetText() == itemName then
				local _, duration, _ = GetContainerItemCooldown(i, j)
				return duration
			end
		end
	end

	return nil
end

--by Crazydru
--返回CD
function Get_CD(name)
	local cd = GetSpellCooldownByName(name)
	if not cd then
		cd = GetInventoryCooldownByName(name)
	end
	if not cd then
		cd = GetContainerItemCooldownByName(name)
	end
	if cd then
		return cd > 0
	end
end

--返回技能蓝量消耗
local function GetSpellCost(spellSlot)
	CM_ScanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
	CM_ScanTip:SetSpell(spellSlot, "spell")
	local _, _, cost = string.find(CM_ScanTipTextLeft2 and CM_ScanTipTextLeft2:GetText() or "", "^(%d+)")
	return tonumber(cost)
end

--返回宠物技能蓝量消耗
local function GetPetSpellCost(spellSlot)
	CM_ScanTip:SetOwner(WorldFrame, "ANCHOR_NONE")
	CM_ScanTip:SetSpell(spellSlot, "pet")
	local _, _, cost = string.find(CM_ScanTipTextLeft2 and CM_ScanTipTextLeft2:GetText() or "", "^(%d+)")
	return tonumber(cost)
end


--返回技能ID
local function GetSpellSlotByName(name)
	local spell, rank = GetSpellInfo(name, bookType)
	--local id = GetSpellIndex(spell, rank)
	return GetSpellIndex(spell, rank)

	--local spell = VanillaPlus.GetSpell(name);

	--return spell and spell.slot or 1;
end

--by Crazydru
--鼠标指向aura
function MoHasAura(aura)
	local unit, m = GetMouseOverUnit()
	if not aura or not unit then
		return false, m
	end
	CM_ScanTip:SetOwner(UIParent, "ANCHOR_NONE")
	aura = string.gsub(aura, "_", " ")
	local i = 1
	while UnitBuff(unit, i) do
		CM_ScanTip:ClearLines()
		CM_ScanTip:SetUnitBuff(unit, i)
		if string.find(CM_ScanTipTextLeft1:GetText(), aura) then
			return true, m
		end
		i = i + 1
	end
	local i = 1
	while UnitDebuff(unit, i) do
		CM_ScanTip:ClearLines()
		CM_ScanTip:SetUnitDebuff(unit, i)
		if string.find(CM_ScanTipTextLeft1:GetText(), aura) then
			return true, m
		end
		i = i + 1
	end
	return false, m
end

--by Crazydru
--损失HP
local function Loss_UnitHp(v, unit)
	local m = false
	if unit == "mouseover" then
		unit, m = GetMouseOverUnit()
	end
	for hp in v do
		local HPLoss = (UnitHealthMax(unit) - UnitHealth(unit)) / 100
		if string.find(hp, ">") then
			local biggerhp = Split(hp, ">")
			return HPLoss > tonumber(biggerhp[2]), m
		elseif string.find(hp, "<") then
			local lesshp = Split(hp, "<")
			return HPLoss < tonumber(lesshp[2]), m
		end
	end
end

--by Crazydru
--损失MP
local function Loss_UnitMp(v, unit)
	local m = false
	if unit == "mouseover" then
		local unit, m = GetMouseOverUnit()
	end

	for mp in v do
		local MPLoss = (UnitManaMax(unit) - UnitMana(unit)) / 100
		if string.find(mp, ">") then
			local biggermp = Split(mp, ">")
			return MPLoss > tonumber(biggermp[2]), m
		elseif string.find(mp, "<") then
			local lessmp = Split(mp, "<")
			return MPLoss < tonumber(lessmp[2]), m
		end
	end
end

--by Crazydru
--攻击目标
local function Check_AttackFor(unit1, unit2)
	unit2 = unit2 or "player"
	if unit1 == "mouseover" then
		local frame = GetMouseFocus() or {}
		if frame.label and frame.id then
			unit1 = frame.label .. frame.id
		end
	end
	if UnitExists(unit1) then
		m = false
		return UnitIsUnit(unit2, unit1 .. "target") == 1, m
	else
		m = true
		return false, m
	end
end

--by Crazydru
--获取角色aura剩余时间及层数（包括debuff、buff）
function MyAuraTimeLeft(aura)
	aura = strlower(aura)
	local timeleft, counts = 0, 0
	for i = 0, 64 do
		CM_ScanTip:SetOwner(UIParent, "ANCHOR_NONE")
		CM_ScanTip:SetPlayerBuff(i)
		local name = CM_ScanTipTextLeft1:GetText()
		if name then
			if strfind(strlower(name), aura) then
				timeleft = GetPlayerBuffTimeLeft(i)
				counts = GetPlayerBuffApplications(i)
				break
			end
		end
		CM_ScanTip:Hide()
	end
	return timeleft, counts
end

--by Crazydru
--获取unit减益类型
function UnitDebuffType(debuff, unit)
	unit = unit or "player"
	CM_ScanTip:SetOwner(UIParent, "ANCHOR_NONE")

	debuff = string.gsub(debuff, "_", " ")
	local m = false
	if unit == "mouseover" then
		unit, m = GetMouseOverUnit()
	end
	if not debuff then
		return false, m
	end
	local debufftype
	local i = 1
	while UnitDebuff(unit, i) do
		_, _, debufftype = UnitDebuff(unit, i)
		if debufftype == "Curse" then
			debufftype = "诅咒"
		end
		if debufftype == "Disease" then
			debufftype = "疾病"
		end
		if debufftype == "Magic" then
			debufftype = "魔法"
		end
		if debufftype == "Poison" then
			debufftype = "中毒"
		end
		if debufftype == debuff then
			return true, m
		end
		i = i + 1
	end
	return false, m
end
--by Crazydru
--获取unit类型
local function UnitType(type, unit)
	unit = unit or "player"
	local m = false
	if unit == "mouseover" then
		unit, m = GetMouseOverUnit()
	end
	if not type then
		return false, m
	end
	return type == UnitCreatureType(unit), m
end

--by Crazydru
--获取所在地类型
local function Get_Zone(zone)
	return zone == GetZoneText()
end

--by Crazydru
--判断是否为boss
local function Check_Boss(unit)
	return UnitClassification(unit) == "worldboss"
end

--by Crazydru
--判断技能是否在施法范围内
local function CheckSpellInRange(spell)
	scantip:SetOwner(WorldFrame, "ANCHOR_NONE")
	scantip:SetAction(22)
	local SoltSpellName = scantipTextLeft1:GetText()
	if SoltSpellName ~= spell then
		if GetSpellIndex(spell) then
			PickupSpell(GetSpellIndex(spell), BOOKTYPE_SPELL)
			PlaceAction(22)
			ClearCursor()
		else
			return false
		end
	end
	return IsActionInRange(22) == 1
end

--判断是否在背后
function CheckIsBehind(time)
	local time = time or 1
	if isbehind == false then
		if GetTime() - behindresettime > time then
			isbehind = true
		end
	end
	return isbehind
end

--判断目标能否流血
function CheckCanBleed(unit)
	if not UnitExists(unit) then
		return false
	end
	local target = UnitName(unit)
	if (UnitCreatureType(unit) == "机械" or UnitCreatureType(unit) == "元素生物") and canbleed[target] == nil then
		canbleed[target] = false
	end
	if canbleed[target] == nil then
		canbleed[target] = true
	end
	if
		(
			UnitHasAura(unit, "血袭")
			or UnitHasAura(unit, "绞喉")
			or UnitHasAura(unit, "割裂")
			or UnitHasAura(unit, "扫击")
			or UnitHasAura(unit, "撕扯")
			or UnitHasAura(unit, "撕裂")
		) and canbleed[target] == false
	then
		canbleed[target] = true
	end
	return canbleed[target]
end

local function GetCurrentShapeshiftForm()
	for index = 1, GetNumShapeshiftForms() do
		local _, _, active = GetShapeshiftFormInfo(index)
		if active then
			return index
		end
	end
end

local function CancelShapeshiftForm(index)
	local index = GetCurrentShapeshiftForm(index)
	if index ~= nil then
		CastShapeshiftForm(index)
	end
end

local function TestConditions(conditions, target)
	local result = true
	local m = false
	for k, v in pairs(conditions) do
		local _, no = string.find(k, "^no")
		local mod = no and string.sub(k, no + 1) or k

		--by Crazydru
		if mod == "help" or mod == "h" then
			result = UnitCanAssist("player", target)
		elseif mod == "exists" or mod == "ex" then
			result = UnitExists(target)
		elseif mod == "harm" or mod == "hr" then
			result = UnitCanAttack("player", target)
		elseif mod == "dead" or mod == "dd" then
			result = UnitIsDead(target) or UnitIsGhost(target)
		elseif mod == "combat" or mod == "cb" then
			result = UnitAffectingCombat("player")
		elseif mod == "stealth" or mod == "sth" then
			result = HasBuff("Ability_Ambush") or HasBuff("Ability_Stealth")
		elseif mod == "move" then
			result = OnMoving()
		elseif mod == "isnpc" then
			result = not UnitIsPlayer(target)
		elseif mod == "mod" or mod == "modifier" then
			if v == true then
				result = IsAltKeyDown() or IsControlKeyDown() or IsShiftKeyDown()
			else
				result = IsAltKeyDown() and v.alt
				result = result or IsControlKeyDown() and v.ctrl
				result = result or IsShiftKeyDown() and v.shift
			end
		--by Crazydru
		elseif mod == "form" or mod == "stance" or mod == "fm" then
			if v == true then
				result = GetCurrentShapeshiftForm() ~= nil
			else
				result = v[GetCurrentShapeshiftForm() or 0]
			end
		elseif mod == "cp" then
			if v then
				for cp in v do
					result = GetCP(cp)
				end
			end
		elseif mod == "cd" then
			if v then
				for cd in v do
					result = Get_CD(cd)
				end
			end
		--by Crazydru
		elseif mod == "mybuff" or mod == "mb" then
			if v then
				for s in v do
					result = UnitHasAura("player", s)
				end
			end
		elseif mod == "mydebuff" or mod == "mdb" then
			if v then
				for s in v do
					result = UnitHasAura("player", s)
				end
			end
		elseif mod == "tarbuff" or mod == "tb" then
			if v then
				for s in v do
					result = UnitHasAura("target", s)
				end
			end
		elseif mod == "tardebuff" or mod == "tdb" then
			if v then
				for s in v do
					result = UnitHasAura("target", s)
				end
			end
		elseif mod == "mouseoverbuff" or mod == "mob" then
			if v then
				for s in v do
					result, m = MoHasAura(s)
				end
			end
		elseif mod == "mouseoverdebuff" or mod == "modb" then
			if v then
				for s in v do
					result, m = MoHasAura(s)
				end
			end
		elseif mod == "mhp" then
			if v then
				result = Compare_UnitHp(v, "player")
			end
		elseif mod == "mmp" then
			if v then
				result = Compare_UnitMp(v, "player")
			end
		elseif mod == "thp" then
			if v then
				result = Compare_UnitHp(v, "target")
			end
		elseif mod == "tmp" then
			if v then
				result = Compare_UnitMp(v, "target")
			end
		elseif mod == "mohp" then
			if v then
				result, m = Compare_UnitHp(v, "mouseover")
			end
		elseif mod == "momp" then
			if v then
				result, m = Compare_UnitMp(v, "mouseover")
			end
		elseif mod == "mlhp" then
			if v then
				result = Loss_UnitHp(v, "player")
			end
		elseif mod == "mlmp" then
			if v then
				result = Loss_UnitMp(v, "player")
			end
		elseif mod == "tlhp" then
			if v then
				result = Loss_UnitHp(v, "target")
			end
		elseif mod == "tlmp" then
			if v then
				result = Loss_UnitMp(v, "target")
			end
		elseif mod == "molhp" then
			if v then
				result, m = Loss_UnitHp(v, "mouseover")
			end
		elseif mod == "molmp" then
			if v then
				result, m = Loss_UnitMp(v, "mouseover")
			end
		elseif mod == "tatk" then
			result = Check_AttackFor("target")
		elseif mod == "moatk" then
			result, m = Check_AttackFor("mouseover")
		elseif mod == "mdbty" then
			for s in v do
				result = UnitDebuffType(s, "player")
			end
		elseif mod == "tdbty" then
			for s in v do
				result = UnitDebuffType(s, "target")
			end
		elseif mod == "modbty" then
			for s in v do
				result, m = UnitDebuffType(s, "mouseover")
			end
		elseif mod == "tty" then
			for s in v do
				result = UnitType(s, "target")
			end
		elseif mod == "moty" then
			for s in v do
				result, m = UnitType(s, "mouseover")
			end
		elseif mod == "ch" then
			result = spellstatus:IsChanneling()
		elseif mod == "zn" then
			if v then
				for s in v do
					result = Get_Zone(s)
				end
			end
		elseif mod == "bs" then
			result = Check_Boss("target")
		elseif mod == "ir" then
			if v then
				for s in v do
					result = CheckSpellInRange(s)
				end
			end
		elseif mod == "ttb" or mod == "ttdb" then
			if v then
				for s in v do
					result = UnitHasAura("targettarget", s)
				end
			end
		elseif mod == "tthp" then
			if v then
				result = Compare_UnitHp(v, "targettarget")
			end
		elseif mod == "ttmp" then
			if v then
				result = Compare_UnitMp(v, "targettarget")
			end
		elseif mod == "ttlhp" then
			if v then
				result = Loss_UnitHp(v, "targettarget")
			end
		elseif mod == "ttlmp" then
			if v then
				result = Loss_UnitMp(v, "targettarget")
			end
		elseif mod == "ttatk" then
			result = Check_AttackFor("targettarget")
		elseif mod == "ttty" then
			for s in v do
				result = UnitType(s, "targettarget")
			end
		elseif mod == "ttdbty" then
			for s in v do
				result = UnitDebuffType(s, "targettarget")
			end
		elseif mod == "php" then
			if v then
				result = Compare_UnitHp(v, "pet")
			end
		elseif mod == "pmp" then
			if v then
				result = Compare_UnitMp(v, "pet")
			end
		elseif mod == "plhp" then
			if v then
				result = Loss_UnitHp(v, "pet")
			end
		elseif mod == "plmp" then
			if v then
				result = Loss_UnitMp(v, "pet")
			end
		elseif mod == "bh" or mod == "behind" then
			if type(v) == "table" then
				for s in v do
					s = tonumber(s)
					result = CheckIsBehind(s)
				end
			else
				result = CheckIsBehind()
			end
		elseif mod == "bl" or mod == "bleed" then
			result = CheckCanBleed("target")

		-- Conditions that are NOT a part of the official implementation.
		elseif mod == "shift" then
			result = IsShiftKeyDown()
		elseif mod == "alt" then
			result = IsAltKeyDown()
		elseif mod == "ctrl" then
			result = IsControlKeyDown()
		elseif mod == "alive" then
			result = not (UnitIsDead(target) or UnitIsGhost())
		else
			return false
		end
		--增加m判定UnitExists("mouseover")
		if no then
			if m then
				result = result
			else
				result = not result
			end
		end

		if not result then
			return false
		end
	end

	return true
end

local UNITS = {
	"(mouseover)",
	"(player)",
	"(pet)",
	"(party)(%d)",
	"(partypet)(%d)",
	"(raid)(%d+)",
	"(raidpet)(%d+)",
	"(target)",
	"(targettarget)",
}

local function IsUnitValid(unit)
	if not unit then
		return
	end
	local offset = 1
	repeat
		local b, e, name, n
		for _, p in ipairs(UNITS) do
			b, e, name, n = string.find(unit, "^" .. p, offset)
			if e then
				break
			end
		end
		if not e then
			return false
		end
		if offset > 1 and name ~= "target" then
			return false
		end
		if n and tonumber(n) == 0 then
			return false
		end

		if (name == "raid" or name == "raidpet") and tonumber(n) > 40 then
			return false
		end

		if (name == "partypet" or name == "party") and tonumber(n) > 4 then
			return false
		end

		offset = e + 1
	until offset > string.len(unit)
	return offset > 1
end

local function GetArg(args)
	for _, arg in ipairs(args) do
		for _, conditionGroup in ipairs(arg.conditionGroups) do
			local target = conditionGroup.target

			local _, _, subTarget = string.find(target, "^mouseover(.*)" and "^mo(.*)")

			if subTarget then
				target = GetMouseOverUnit()
			end
			if not IsUnitValid(target) then
				target = "target"
			end

			local result = TestConditions(conditionGroup.conditions, target)
			if result then
				return arg, target, conditionGroup.target
			end
		end
	end
end

local function ParseArguments(s)
	local args = {}

	for _, sarg in ipairs(Split(s, ";")) do
		local arg = {
			conditionGroups = {},
		}
		table.insert(args, arg)

		local offset = 1
		repeat
			local _, e, sconds = string.find(sarg, "%s*%[([^]]*)]%s*", offset)
			if not sconds then
				break
			end

			local conditionGroup = {
				target = "target",
				conditions = {},
			}
			table.insert(arg.conditionGroups, conditionGroup)

			for _, scond in ipairs(Split(sconds, ",")) do
				local _, _, a, k, q, b, l, v = string.find(scond, "^%s*(@?)(%w+)(:?)(>?)(<?)([^%s]*)%s*$")

				if a then
					if a == "@" and q == "" and v == "" then
						conditionGroup.target = k
					elseif a == "" then
						if q == ":" then
							local conds = {}
							for _, smod in ipairs(Split(v, "/")) do
								if smod ~= "" then
									conds[tonumber(smod) or string.lower(smod)] = true
								end
							end
							conditionGroup.conditions[string.lower(k)] = conds
						else
							conditionGroup.conditions[string.lower(k)] = true
						end
						if b == ">" then
							local conds = {}
							for _, smod in ipairs(Split(v, ">")) do
								if smod ~= "" then
									conds[tonumber(b .. smod) or string.lower(b .. smod)] = true
								end
							end
							conditionGroup.conditions[string.lower(k)] = conds
						elseif l == "<" then
							local conds = {}
							for _, smod in ipairs(Split(v, "<")) do
								if smod ~= "" then
									conds[tonumber(l .. smod) or string.lower(l .. smod)] = true
								end
							end
							conditionGroup.conditions[string.lower(k)] = conds
						end
					end
				end
			end

			offset = e + 1
		until false

		arg.text = Trim(string.sub(sarg, offset))

		if table.getn(arg.conditionGroups) == 0 then
			local conditionGroup = {
				target = "target",
				conditions = {},
			}
			table.insert(arg.conditionGroups, conditionGroup)
		end
	end

	return args
end

local COMMANDS = {
	["/cast"] = function(args)
		for _, arg in ipairs(args) do
			arg.spellSlot, arg.bookType = GetSpellSlotByName(arg.text)
		end
	end,

	["/castsequence"] = function(args)
		sequence = args[1]
		if not sequence then
			return
		end

		sequence.index = 1
		sequence.reset = {}
		sequence.spells = {}
		sequence.bookTypes = {}
		sequence.status = 0

		local _, e, reset = string.find(sequence.text, "^%s*reset=([%w/]+)%s*")
		s = e and string.sub(sequence.text, e + 1) or sequence.text

		if reset then
			for _, rule in ipairs(Split(reset, "/")) do
				local secs = tonumber(rule)
				if secs and secs > 0 then
					if not sequence.reset.secs or secs < sequence.reset.secs then
						sequence.reset.secs = secs
					end
				else
					sequence.reset[rule] = true
				end
			end
		end

		for _, name in ipairs(Split(s, ",")) do
			local spellSlot, bookType = GetSpellSlotByName(Trim(name))
			if name and GetSpellSlotByName(Trim(name)) then
				table.insert(sequence.spells, spellSlot)
				table.insert(sequence.bookTypes, bookType)
			end
		end
	end,
	--下列语句加载报错，根据凡人插件重构
	["/use"] = function(args)
		--for itemID, item in pairs(items) do
		--for _, arg in ipairs(args) do
		--if arg and ( string.lower(arg.text) == string.lower(item.name) or string.lower(arg.text) == item.id ) then
		--arg.itemID = itemID
		--end
		--end
		--end
		for itemID, item in pairs(items) do
			if item["name"] then
				for _, arg in ipairs(args) do
					if
						arg
						and (
							(string.lower(arg.text) == string.lower(item.name))
							or (string.lower(arg.text) == item["id"])
						)
					then
						arg["itemID"] = itemID
					end
				end
			end
		end
	end,

	["/target"] = true,

	["/stopmacro"] = true,
}

local function ParseMacro(name)
	local macroIndex = GetMacroIndexByName(name)
	if macroIndex == 0 then
		return nil
	end

	local name, iconTexture, body = GetMacroInfo(macroIndex)
	if not name then
		return nil
	end

	local macro = {
		name = name,
		iconTexture = iconTexture,
		commands = {},
	}

	for i, line in ipairs(Split(body, "\n", true)) do
		if i == 1 then
			--by Crazydru
			local _, _, s = string.find(line, "^%s*#(.*)$")
			if s and not string.find(s, "^%w") then
				macro.tooltips = {}
				for _, arg in ipairs(ParseArguments(s)) do
					local tooltip = {
						conditionGroups = arg.conditionGroups,
						spellSlot, bookType = GetSpellSlotByName(Trim(arg.text)),
					}
					table.insert(macro.tooltips, tooltip)
				end
			end
			local _, _, s = string.find(line, "^%s*#show(.*)$")
			if s and not string.find(s, "^%w") then
				macro.tooltips = {}
				for _, arg in ipairs(ParseArguments(s)) do
					local tooltip = {
						conditionGroups = arg.conditionGroups,
						spellSlot, bookType = GetSpellSlotByName(Trim(arg.text)),
					}
					table.insert(macro.tooltips, tooltip)
				end
			end
			local _, _, s = string.find(line, "^%s*#showtooltip(.*)$")
			if s and not string.find(s, "^%w") then
				macro.tooltips = {}
				for _, arg in ipairs(ParseArguments(s)) do
					local tooltip = {
						conditionGroups = arg.conditionGroups,
						spellSlot, bookType = GetSpellSlotByName(Trim(arg.text)),
					}
					table.insert(macro.tooltips, tooltip)
				end
			end
		end

		if i > 1 or not macro.tooltips then
			local _, e, name = string.find(line, "^(/%w+)%s*")
			if name then
				local command = {
					name = name,
					text = string.sub(line, e + 1),
				}

				table.insert(macro.commands, command)

				local cmd = COMMANDS[name]
				if cmd then
					command.args = ParseArguments(command.text)
					if type(cmd) == "function" then
						cmd(command.args)
					end
					if name == "/castsequence" then
						table.insert(sequences, command.args[1])
					end
				end

				-- Search for a corresponding slash command.
				for cmd, fn in pairs(SlashCmdList) do
					for i in Seq do
						local cmdt = _G["SLASH_" .. cmd .. i]
						if not cmdt then
							break
						end
						if cmdt == name then
							command.fn = fn
							break
						end
					end
					if command.fn then
						break
					end
				end
			elseif line ~= "" then
				table.insert(macro.commands, { text = line })
			end
		end
	end

	return macro
end

--by Crazydru 增加bookType信息
local function GetMacroInfo(macro)
	if macro.tooltips then
		local arg = GetArg(macro.tooltips)
		if arg and arg.spellSlot then
			return arg.bookType, arg.spellSlot, GetSpellTexture(arg.spellSlot, arg.bookType)
		end
	end

	for _, command in ipairs(macro.commands) do
		if command.name == "/cast" then
			local arg = GetArg(command.args)
			if arg and arg.spellSlot then
				return arg.bookType, arg.spellSlot, GetSpellTexture(arg.spellSlot, arg.bookType)
			end
		elseif command.name == "/castsequence" then
			local arg = GetArg(command.args)
			if arg then
				local reset = false
				reset = arg.reset.shift and IsShiftKeyDown()
				reset = reset or (arg.reset.alt and IsAltKeyDown())
				reset = reset or (arg.reset.ctrl and IsControlKeyDown())

				local spellSlot = arg.spells[reset and 1 or arg.index]
				local bookType = arg.bookTypes[reset and 1 or arg.index]

				if spellSlot then
					return bookType, spellSlot, GetSpellTexture(spellSlot, bookType)
				end
			end
		elseif command.name == "/stopmacro" then
			if GetArg(command.args) then
				break
			end
		elseif command.name == "/use" then
			local arg = GetArg(command.args)
			if arg and arg.itemID and items[arg.itemID] then
				return "item", arg.itemID, items[arg.itemID].texture
			end
		end
	end
end

local function GetMacro(name)
	local macro = macros[name]
	if macro then
		return macro
	end
	macros[name] = ParseMacro(name)
	return macros[name]
end

local function RunMacro(macro)
	for _, command in ipairs(macro.commands) do
		if command.fn then
			local r = command.fn(command.text, command)
			if r ~= nil and r == false then
				break
			end
		else
			if command.name then
				ChatFrameEditBox:SetText(command.name .. " " .. command.text)
				ChatEdit_SendText(ChatFrameEditBox)
			else
				ChatFrameEditBox:SetText(command.text)
				ChatEdit_SendText(ChatFrameEditBox)
			end
		end
	end
end

--by Crazydru 增加bookType信息
local function RefreshAction(action)
	local spellSlot, itemID = action.spellSlot, action.itemID
	local type, value, texture = GetMacroInfo(action.macro)

	action.texture = texture
	
	if type == "spell" then
		action.cost = GetSpellCost(value)
		action.usable = (not action.cost) or (UnitMana("player") >= action.cost)
		action.itemID = nil
		action.spellSlot = value
		action.bookType = type
	elseif type == "pet" then
		action.cost = GetPetSpellCost(value)
		action.usable = (not action.cost) or (UnitMana("pet") >= action.cost)
		action.itemID = nil
		action.spellSlot = value
		action.bookType = type
	elseif type == "item" then
		action.cost = 0
		action.usable = true
		action.itemID = value
		action.spellSlot = nil
		action.bookType = nil
	else
		action.cost = 0
		action.usable = true
		action.itemID = nil
		action.spellSlot = nil
		action.bookType = nil
	end

	return usable ~= action.usable or spellSlot ~= action.spellSlot or itemID ~= action.itemID or bookType ~= action.bookType
end

local function GetAction(slot)
	local action = actions[slot]
	if action then
		return action
	end

	local text = GetActionText(slot)

	if text then
		local macro = GetMacro(text)
		if macro then
			action = {
				text = text,
				macro = macro,
				slot = slot,
			}

			RefreshAction(action)
			actions[slot] = action
			return action
		end
	end
end

local function SendEventForAction(slot, event, ...)
	local _this = this

	arg1, arg2, arg3, arg4, arg5, arg6, arg7 = unpack(arg)

	local page = floor((slot - 1) / NUM_ACTIONBAR_BUTTONS) + 1
	local pageSlot = slot - (page - 1) * NUM_ACTIONBAR_BUTTONS

	-- Classic support.

	if slot >= 73 then
		this = _G["BonusActionButton" .. pageSlot]
		if this then
			ActionButton_OnEvent(event)
		end
	else
		if slot >= 61 then
			this = _G["MultiBarBottomLeftButton" .. pageSlot]
		elseif slot >= 49 then
			this = _G["MultiBarBottomRightButton" .. pageSlot]
		elseif slot >= 37 then
			this = _G["MultiBarLeftButton" .. pageSlot]
		elseif slot >= 25 then
			this = _G["MultiBarRightButton" .. pageSlot]
		else
			this = nil
		end

		if this then
			ActionButton_OnEvent(event)
		end

		if page == CURRENT_ACTIONBAR_PAGE then
			this = _G["ActionButton" .. pageSlot]
			if this then
				ActionButton_OnEvent(event)
			end
		end
	end

	this = _this

	for _, fn in ipairs(actionEventHandlers) do
		fn(slot, event, unpack(arg))
	end
end

local function IndexItems()
	items = {}
	for bagID = 0, NUM_BAG_SLOTS do
		for slot = GetContainerNumSlots(bagID), 1, -1 do
			local link = GetContainerItemLink(bagID, slot)
			if link then
				local _, _, itemID = string.find(link, "item:(%d+)")
				if itemID and not items[itemID] then
					local name, _, _, _, _, _, _, _, texture = GetItemInfo(itemID)
					local item = {
						bagID = bagID,
						slot = slot,
						id = itemID,
						name = name,
						texture = texture,
					}
					_, _, item.link = string.find(link, "|H([^|]+)|h")
					items[itemID] = item
				end
			end
		end
	end

	for inventoryID = 0, 19 do
		local link = GetInventoryItemLink("player", inventoryID)
		if link then
			local _, _, itemID = string.find(link, "item:(%d+)")
			if itemID and not items[itemID] then
				local name, _, _, _, _, _, _, _, texture = GetItemInfo(itemID)
				local item = {
					inventoryID = inventoryID,
					id = itemID,
					name = name,
					texture = texture,
				}
				_, _, item.link = string.find(link, "|H([^|]+)|h")
				items[itemID] = item
			end
		end
	end
end

--------------------------------------------------------------------------------
-- Overrides                                                                   -
--------------------------------------------------------------------------------

local base = {}

base.UseAction = UseAction
function UseAction(slot, checkCursor, onSelf)
	local action = GetAction(slot)
	if action and action.macro then
		RunMacro(action.macro)
	else
		base.UseAction(slot, checkCursor, onSelf)
	end
end

base.GameTooltip = {}

base.GameTooltip.SetAction = GameTooltip.SetAction
--by Crazydru增加booktype，正确识别pet技能
function GameTooltip.SetAction(self, slot)
	local action = GetAction(slot)
	if action then
		if action.spellSlot then
			GameTooltip:SetSpell(action.spellSlot, action.bookType)
			local _, rank = GetSpellName(action.spellSlot, action.bookType)
			GameTooltipTextRight1:SetText("|cff808080" .. rank .. "|r")
			GameTooltipTextRight1:Show()
			GameTooltip:Show()
		elseif action.itemLink then
			GameTooltip:SetHyperlink(action.itemLink)
			GameTooltip:Show()
		end
	else
		base.GameTooltip.SetAction(self, slot)
	end
end

base.IsActionInRange = IsActionInRange
function IsActionInRange(slot, unit)
	if slot then
		local action = GetAction(slot)
	end
	if action and action.macro and action.macro.tooltips then
		return action.spellSlot and true
	else
		return base.IsActionInRange(slot, unit)
	end
end

base.IsUsableAction = IsUsableAction
function IsUsableAction(slot, unit)
	local action = GetAction(slot)
	if action and action.macro and action.macro.tooltips then
		if action.usable then
			return true, false
		else
			return false, true
		end
	else
		return base.IsUsableAction(slot, unit)
	end
end

base.GetActionTexture = GetActionTexture
function GetActionTexture(slot)
	local action = GetAction(slot)
	if action and action.macro and action.macro.tooltips then
		return action.texture or "Interface\\Icons\\INV_Misc_QuestionMark"
	else
		return base.GetActionTexture(slot)
	end
end

base.GetActionCooldown = GetActionCooldown
--by Crazydru增加booktype，正确识别pet技能
function GetActionCooldown(slot)
	local action = GetAction(slot)
	if action and action.macro then
		if action.spellSlot then
			return GetSpellCooldown(action.spellSlot, action.bookType)
		elseif action.itemID then
			local item = items[action.itemID]
			if item then
				if item.bagID and item.slot then
					return GetContainerItemCooldown(item.bagID, item.slot)
				elseif item.inventoryID then
					return GetInventoryItemCooldown("player", item.inventoryID)
				end
			end
		end
		return 0, 0, 0
	else
		return base.GetActionCooldown(slot)
	end
end

base.SlashCmdList = {}

base.SlashCmdList.TARGET = SlashCmdList["TARGET"]
SlashCmdList["TARGET"] = function(msg)
	local arg, target = GetArg(command and command.args or ParseArguments(msg))
	if arg then
		if target ~= "target" then
			TargetUnit(target)
		else
			base.SlashCmdList.TARGET(arg.text)
		end
	end
end

--------------------------------------------------------------------------------
-- UI                                                                          -
--------------------------------------------------------------------------------

local function OnUpdate(self)
	local time = GetTime()

	-- 技能刷新时间Slow down a bit.
	if (time - lastUpdate) < 0.1 then
		return
	end
	lastUpdate = time

	if currentSequence and currentSequence.status >= 2 and (time - currentSequence.lastUpdate) >= 0.2 then
		if currentSequence.status == 2 then
			if currentSequence.index >= table.getn(currentSequence.spells) then
				currentSequence.index = 1
			else
				currentSequence.index = currentSequence.index + 1
			end
		end

		for slot, action in pairs(actions) do
			for _, command in ipairs(action.macro.commands) do
				if command.name == "/castsequence" and command.args[1] == currentSequence then
					SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
				end
			end
		end

		currentSequence = nil
	end

	for _, sequence in ipairs(sequences) do
		if sequence.index > 1 and sequence.reset.secs and (time - sequence.lastUpdate) >= sequence.reset.secs then
			sequence.index = 1

			for slot, action in pairs(actions) do
				for _, command in ipairs(action.macro.commands) do
					if command.name == "/castsequence" and command.args[1] == sequence then
						SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
					end
				end
			end
		end
	end

	for slot, action in pairs(actions) do
		if RefreshAction(action) then
			--if actions[arg1] then
			SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
			--end
		end
	end
end

local function OnEvent()
	if event == "UPDATE_MACROS" or event == "SPELLS_CHANGED" then
		currentSequence = nil
		macros = {}
		actions = {}
		sequences = {}
		IndexItems()
	elseif event == "ACTIONBAR_SLOT_CHANGED" then
		actions[arg1] = nil
		SendEventForAction(arg1, "ACTIONBAR_SLOT_CHANGED", arg1)
	elseif event == "BAG_UPDATE" then
		macros = {}
		actions = {}
		IndexItems()
	elseif event == "PLAYER_LEAVE_COMBAT" then
		for _, sequence in pairs(sequences) do
			if currentSequence ~= sequence and sequence.index > 1 and sequence.reset.combat then
				sequence.index = 1
			end
		end
	elseif event == "PLAYER_TARGET_CHANGED" then
		for _, sequence in pairs(sequences) do
			if currentSequence ~= sequence and sequence.index > 1 and sequence.reset.target then
				sequence.index = 1
			end
		end
	elseif currentSequence then
		if event == "SPELLCAST_START" then
			currentSequence.status = 1
		elseif event == "SPELLCAST_STOP" then
			currentSequence.status = 2
			currentSequence.lastUpdate = GetTime()
		elseif event == "SPELLCAST_FAILED" or event == "SPELLCAST_INTERRUPTED" then
			currentSequence.status = 3
		end

	--by Crazydru
	elseif event == "UI_ERROR_MESSAGE" then
		if arg1 == "你必须位于目标背后" then
			isbehind = false
			behindresettime = GetTime()
		end
	elseif event == "CHAT_MSG_SPELL_SELF_DAMAGE" then
		local _, _, spell, target = string.find(arg1, "你的(.+)施放失败。(.+)对此免疫")
		if spell and target then
			if
				(spell == "撕扯" or spell == "割裂" or spell == "绞喉" or spell == "撕裂")
				and canbleed[target] == true
			then
				canbleed[target] = false
			elseif not spell == "精灵之火（野性）" then
				canbleed[target] = true
			end
		end
		for _, test in pairs({
			"你的(.+)没有击中(.+)",
			"你的(.+)被(.+)躲闪过去了",
			"你的(.+)被(.+)招架了",
			"你的(.+)被(.+)格挡了",
		}) do
			local _, _, foundSpell, foundTarget = string.find(arg1, test)
			if foundSpell and foundTarget then
				break
			end
		end
		if foundSpell and foundTarget then
			if
				(foundSpell == "撕扯" or foundSpell == "割裂" or foundSpell == "绞喉" or foundSpell == "撕裂")
				and canbleed[target] == false
			then
				canbleed[target] = true
			end
		end
	elseif event == "CHAT_MSG_COMBAT_SELF_HITS" then
		--print(arg1,arg2,arg3,arg4)
	end
end
local f = CreateFrame("Frame")
f:SetScript("OnUpdate", OnUpdate)
CM_ScanTip = CreateFrame("GameTooltip", "CM_ScanTip", nil, "GameTooltipTemplate")
CM_ScanTip:SetScript("OnEvent", OnEvent)

CM_ScanTip:RegisterEvent("UPDATE_MACROS")
CM_ScanTip:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
CM_ScanTip:RegisterEvent("SPELLCAST_START")
CM_ScanTip:RegisterEvent("SPELLCAST_STOP")
CM_ScanTip:RegisterEvent("SPELLCAST_FAILED")
CM_ScanTip:RegisterEvent("SPELLCAST_INTERRUPTED")
CM_ScanTip:RegisterEvent("PLAYER_LEAVE_COMBAT")
CM_ScanTip:RegisterEvent("PLAYER_TARGET_CHANGED")
CM_ScanTip:RegisterEvent("SPELLS_CHANGED")
CM_ScanTip:RegisterEvent("BAG_UPDATE")
--by Crazydru 增加监听中央红字、自己造成的伤害、平砍
CM_ScanTip:RegisterEvent("UI_ERROR_MESSAGE")
CM_ScanTip:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
CM_ScanTip:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS")
CM_ScanTip:RegisterEvent("CHAT_MSG_COMBAT_SELF_MISSES")

--------------------------------------------------------------------------------
-- Slash Commands                                                              -
--------------------------------------------------------------------------------
SlashCmdList["CAST"] = function(msg, command)
	local args = command and command.args
	if not args then
		args = ParseArguments(msg)
		COMMANDS["/cast"](args)
	end

	--arg
	--target 返回实际的单位
	local arg, target, cmd = GetArg(args)

	--选择目标
	local retarget = target and not UnitIsUnit(target, "target")
	if retarget then
		TargetUnit(target)
	end

	--一般施放技能
	if arg and arg.spellSlot then
		local spellName, spellRank = GetSpellName(arg.spellSlot, arg.bookType)
		if cmd == "focus" then
			SlashCmdList.FCAST(spellName .. "(" .. spellRank .. ")")
		else
			CastSpellByName(spellName .. "(" .. spellRank .. ")")
		end
	else
		if msg ~= "" then
			CastSpellByName(msg)
		end
	end

	--是否返回上一个目标
	if retarget then
		TargetLastTarget()
	end

	--print(cmd.." / "..target)
end

SlashCmdList["USE"] = function(msg, command)
	local args = command and command.args
	if not args then
		args = ParseArguments(msg)
		COMMANDS["/use"](args)
	end

	local arg, target, cmd = GetArg(args)
	if not arg or not arg.itemID then
		return
	end

	local item = items[arg.itemID]
	if not item then
		return
	end

	local retarget = target and not UnitIsUnit(target, "target")
	if retarget then
		TargetUnit(target)
	end

	if item.name and cmd == "focus" then
		SlashCmdList.FITEM(item.name)
	elseif item.bagID and item.slot then
		UseContainerItem(item.bagID, item.slot)
	elseif item.inventoryID then
		UseInventoryItem(item.inventoryID)
	end

	--是否返回上一个目标
	if retarget then
		TargetLastTarget()
	end
end

SlashCmdList["CASTSEQUENCE"] = function(msg, command)
	local args = command and command.args

	if currentSequence then
		return
	end

	if not args then
		args = ParseArguments(msg)
		COMMANDS["/castsequence"](args)
	end

	local arg, target, cmd = GetArg(args)
	if not arg then
		return
	end

	if arg and arg.index > 1 then
		local reset = false
		reset = arg.reset.shift and IsShiftKeyDown()
		reset = reset or (arg.reset.alt and IsAltKeyDown())
		reset = reset or (arg.reset.ctrl and IsControlKeyDown())
		if reset then
			arg.index = 1
		end
	end

	local spellSlot = arg.spells[arg.index]
	local bookType = arg.bookTypes[arg.index]

	if spellSlot then
		arg.status = 0
		arg.lastUpdate = GetTime()

		currentSequence = arg

		local retarget = target and not UnitIsUnit(target, "target")
		if retarget then
			TargetUnit(target)
		end

		local spellName, spellRank = GetSpellName(spellSlot, bookType)
		if cmd == "focus" then
			SlashCmdList.FCAST(spellName .. "(" .. spellRank .. ")")
		else
			CastSpellByName(spellName .. "(" .. spellRank .. ")")
		end

		--是否返回上一个目标
		if retarget then
			TargetLastTarget()
		end
	end
end

SlashCmdList["STOPMACRO"] = function(msg, command)
	if command and GetArg(command.args) then
		return false
	end
end

SlashCmdList["CANCELFORM"] = function(msg)
	local arg = GetArg(command and command.args or ParseArguments(msg))
	if arg then
		CancelShapeshiftForm()
	end
end

SLASH_CANCELFORM1 = "/cancelform"
SLASH_CASTSEQUENCE1 = "/castsequence"
SLASH_STOPMACRO1 = "/stopmacro"
SLASH_USE1 = "/use"
SLASH_USE2 = "/equip"

-- Exports

CleverMacro = {}

CleverMacro.RegisterActionEventHandler = function(fn)
	if type(fn) == "function" then
		table.insert(actionEventHandlers, fn)
	end
end

CleverMacro.RegisterMouseOverResolver = function(fn)
	if type(fn) == "function" then
		table.insert(mouseOverResolvers, fn)
	end
end